import functools
import itertools
import json
from pathlib import Path

import jsonschema
from django.core.exceptions import ValidationError
from django.utils.deconstruct import deconstructible

from sysreptor.utils.fielddefinition.types import (
    FieldDefinition,
)
from sysreptor.utils.fielddefinition.validators import FieldDefinitionValidator


@functools.cache
def get_section_definition_schema():
    return jsonschema.Draft202012Validator(schema=json.loads((Path(__file__).parent / 'sectiondefinition.schema.json').read_text()))


@functools.cache
def get_finding_ordering_schema():
    return jsonschema.Draft202012Validator(schema=json.loads((Path(__file__).parent / 'findingordering.schema.json').read_text()))


@functools.cache
def get_defaultnotes_schema():
    return jsonschema.Draft202012Validator(schema=json.loads((Path(__file__).parent / 'defaultnotes.schema.json').read_text()))



@deconstructible
class SectionDefinitionValidator:
    def __init__(self, core_fields: FieldDefinition|None = None, predefined_fields: FieldDefinition|None = None) -> None:
        self.core_fields = core_fields

    def __call__(self, value: list[dict]):
        try:
            get_section_definition_schema().validate(value)
        except jsonschema.ValidationError as ex:
            raise ValidationError('Invalid section definition') from ex

        # validate unique section IDs
        section_ids = [s['id'] for s in value]
        duplicate_ids = set([f for f in section_ids if section_ids.count(f) > 1])
        if duplicate_ids:
            raise ValidationError(f'Invalid section definition: Section IDs are not unique. Duplicate IDs: {", ".join(duplicate_ids)}')

        # validate field definition and unique field IDs across all sections
        all_fields = list(itertools.chain(*map(lambda s: s['fields'], value)))
        FieldDefinitionValidator(core_fields=self.core_fields)(all_fields)


@deconstructible
class FindingOrderingValidator:
    def __call__(self, value: list[dict]):
        try:
            get_finding_ordering_schema().validate(value)
        except jsonschema.ValidationError as ex:
            raise ValidationError('Invalid finding ordering') from ex


@deconstructible
class FindingGroupingValidator:
    def __call__(self, value: list[dict]|None):
        if not value:
            return

        try:
            # Currently the sorting and grouping definition use the same format
            get_finding_ordering_schema().validate(value)
        except jsonschema.ValidationError as ex:
            raise ValidationError('Invalid finding grouping') from ex


@deconstructible
class DefaultNotesValidator:
    def __call__(self, value: list[dict]):
        try:
            get_defaultnotes_schema().validate(value)
        except jsonschema.ValidationError as ex:
            raise ValidationError('Invalid default notes') from ex

        # Validate unique note IDs
        note_ids = set(map(lambda n: n['id'], value))
        if len(value) != len(note_ids):
            raise ValidationError('Invalid default notes: Note IDs are not unique')

        # Validate parent ID exists
        for note in value:
            if note.get('parent') is not None and note.get('parent') not in note_ids:
                raise ValidationError(f'Invalid default notes: Parent ID "{note["parent"]}" does not exist')

        # Validate parent-child relationships form a valid tree
        from sysreptor.pentests.querysets import NotebookPageManagerBase
        NotebookPageManagerBase().check_parent_and_order(instances=value)
